//////////////////////////////////////
// Fast Container Library           //
// MIT License - Copyright (c) 2012 //
//////////////////////////////////////

#pragma once

#include "dynamic_array.h"

FC_NAMESPACE_BEGIN


template <class T, class Allocator>
class dynamic_array2d : public dynamic_array<T, Allocator>
{
public:
	typedef dynamic_array<T, Allocator>					base_type;
	typedef dynamic_array2d<T, Allocator>				this_type;
	typedef typename base_type::allocator_type			allocator_type;
	typedef typename base_type::size_type				size_type;
	typedef typename base_type::value_type				value_type;
	typedef typename base_type::reference				reference;
	typedef typename base_type::const_reference			const_reference;
	typedef typename base_type::iterator				iterator;
	typedef typename base_type::const_iterator			const_iterator;

	using base_type::size;
	using base_type::empty;
	using base_type::data;
	using base_type::front;
	using base_type::back;
	using base_type::get_allocator;
	using base_type::set_allocator;
	using base_type::fill;

	using base_type::m_allocator;
	using base_type::m_data;
	using base_type::m_size;

	dynamic_array2d()
		: base_type(), m_x(0), m_y(0)
		{
		}

	explicit dynamic_array2d( const allocator_type& alloc )
		: base_type(alloc), m_x(0), m_y(0)
		{
		}

	explicit dynamic_array2d( size_t y, size_t x, const T& value = T(), const allocator_type& alloc = allocator_type() )
		: base_type(x * y, value, alloc), m_x(x), m_y(y)
		{
		}

	dynamic_array2d( const this_type& x )
		: base_type(x), m_x(x.m_x), m_y(x.m_y)
		{
		}

	reference		operator ()( size_t y, size_t x )		{ return m_data[ offset(y, x) ]; }
	const_reference operator ()( size_t y, size_t x ) const	{ return m_data[ offset(y, x) ]; }

	reference at( size_t y, size_t x )
	{
		FC_ASSERT(y < m_y && x < m_x);
		return m_data[ offset(y, x) ];
	}

	const_reference at( size_t y, size_t x ) const
	{
		FC_ASSERT(y < m_y && x < m_x);
		return m_data[ offset(y, x) ];
	}

	iterator		iterator_at( size_t y, size_t x )		{ return m_data + offset(y, x); }
	const_iterator	iterator_at( size_t y, size_t x ) const	{ return m_data + offset(y, x); }

	FC_FORCE_INLINE size_t offset( size_t y, size_t x ) const { return (x + y * m_x); }

	size_t x() const { return m_x; }
	size_t y() const { return m_y; }

	void resize( size_t y, size_t x )
	{
		const size_t n = x * y;
		T* ptr = 0;

		if( n > 0 )
		{
			ptr = (T*)base_type::allocate(n);
			if( m_data )
			{
				const size_t width = fc::min(x, m_x);
				const size_t height = fc::min(y, m_y);

				//resizes an array while maintaining previous dimension-ordered data.
				for( size_t i(0); i < height; ++i )
				{
					const_iterator it = iterator_at(i, 0);
					fc::copy( it, it + width, ptr + (i * width) );
				}
			}
		}

		base_type::deallocate();
		m_data = ptr;
		m_size = n;
		m_x = x;
		m_y = y;
	}

	void write_region( size_t x, size_t y, size_t width, size_t height, const this_type& region_to_copy )
	{
		FC_ASSERT(x <= m_x);
		FC_ASSERT(y <= m_y);
		FC_ASSERT(width <= region_to_copy.x());
		FC_ASSERT(height <= region_to_copy.y());

		if(x + width > m_x)
			width = m_x - x;
		if(y + height > m_y)
			height = m_y - y;

		for( size_t i(0); i < height; ++i )
		{
			const_iterator it = region_to_copy.iterator_at(i, 0);
			fc::copy(it, it + width, iterator_at(y + i, x));
		}
	}

	void copy_region( size_t x, size_t y, size_t width, size_t height, this_type& out_region ) const
	{
		FC_ASSERT(x + width <= m_x);
		FC_ASSERT(y + height <= m_y);

		if(out_region.x() < width || out_region.y() < height)
			out_region.resize(height, width);

		for( size_t i(0); i < height; ++i )
		{
			const_iterator it = iterator_at(y + i, x);
			fc::copy(it, it + width, out_region.iterator_at(i, 0));
		}
	}

	this_type subarray( size_t x, size_t y, size_t width, size_t height ) const
	{
		this_type x;
		copy_region(x, y, width, height, x);
		return x;
	}

	this_type &operator =( const this_type &x )
	{
		base_type::operator =(x);
		m_x = x.m_x;
		m_y = x.m_y;
		return *this;
	}

	void swap( this_type& x )
	{
		base_type::swap(x);
		fc::swap( m_x, x.m_x );
		fc::swap( m_y, x.m_y );
	}

protected:
	size_t m_x, m_y;
};


template <class T, class Allocator> inline
	void swap( dynamic_array2d<T, Allocator>& a, dynamic_array2d<T, Allocator>& b )
	{
		a.swap(b);
	}

template <class T> inline
	bool operator ==( const dynamic_array2d<T>& a, const dynamic_array2d<T>& b )
	{
		return( &a == &b || (a.x() == b.x() && a.y() == b.y() && fc::equal(a.begin(), a.end(), b.begin())) );
	}

template <class T> inline
	bool operator !=( const dynamic_array2d<T>& a, const dynamic_array2d<T>& b )
	{
		return !(a == b);
	}



FC_NAMESPACE_END
